package org.rcgwt.client.ui.calendar;

import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.rcgwt.client.ui.calendar.controller.CalendarAction;
import org.rcgwt.client.ui.calendar.controller.CalendarChangesMask;
import org.rcgwt.client.ui.calendar.controller.CalendarController;
import org.rcgwt.client.ui.calendar.event.HasInvalidDateHandlers;
import org.rcgwt.client.ui.calendar.event.HasSelectionChangedHandlers;
import org.rcgwt.client.ui.calendar.event.InvalidDateHandler;
import org.rcgwt.client.ui.calendar.event.SelectionChangedHandler;
import org.rcgwt.client.ui.calendar.model.impl.CalendarModel;
import org.rcgwt.client.ui.calendar.model.impl.Event;
import org.rcgwt.client.ui.calendar.model.impl.SelectionCardinality;
import org.rcgwt.client.ui.calendar.mvc.CalendarContext;
import org.rcgwt.client.ui.calendar.mvc.ICalendarContext;
import org.rcgwt.client.ui.calendar.renderer.IRenderer;
import org.rcgwt.client.ui.themes.ITheme;
import org.rcgwt.client.ui.themes.StandardTheme;
import org.rcgwt.client.ui.themes.StandardTheme.RendererLevel;
import org.rcgwt.client.ui.themes.StandardTheme.RendererType;

import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FocusPanel;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.VerticalPanel;

/**
 * A rich calendar component. It can display & manipulate dates. It have some
 * accessibility features. It can also support events of many types.
 * 
 * @author Elian ORIOU
 */

public class RichCalendar extends Composite implements
		HasValueChangeHandlers<Date>, HasSelectionChangedHandlers,
		HasInvalidDateHandlers {
	/* Controls */
	private FocusPanel rootPanel;
	private VerticalPanel renderersPanel;

	/* MVC Components */
	private ITheme theme;
	private Map<RendererType, IRenderer> renderers;
	private CalendarController controller;
	private CalendarModel model;
	private ICalendarContext calendarContext;

	private boolean weekNumber;
	private boolean semanticNavigation;

	/**
	 * The Default constructor
	 */

	public RichCalendar() {
		this(RendererLevel.SHORT, false);
	}

	public RichCalendar(RendererLevel level, boolean weekNumber) {
		this(level, SelectionCardinality.SINGLE, weekNumber, false);
	}

	public RichCalendar(RendererLevel level,
			SelectionCardinality selectionCardinality, boolean weekNumber,
			boolean semanticNavigation) {
		this(level, selectionCardinality, weekNumber, semanticNavigation,
				new StandardTheme(), new CalendarModel());
	}

	public RichCalendar(RendererLevel level,
			SelectionCardinality selectionCardinality, boolean weekNumber,
			boolean semanticNavigation, ITheme theme,
			CalendarModel calendarModel) {
		this.theme = theme;
		this.model = calendarModel;
		this.model.setSelectionCardinality(selectionCardinality);
		this.weekNumber = weekNumber;
		this.semanticNavigation = semanticNavigation;

		renderers = theme.getRenderers(level);
		controller = new CalendarController(this, theme, model, renderers);

		calendarContext = new CalendarContext(theme, model, renderers,
				controller);

		this.render();
		this.initWidget(rootPanel);
	}

	/**
	 * Component rendering method
	 */

	private void render() {
		/* Main Panel */
		rootPanel = new FocusPanel();
		rootPanel.setStyleName(theme.getMainStyle().getClassName());

		/* Renderer Panel */
		renderersPanel = new VerticalPanel();

		/* Renderer */

		/* HEADER */
		renderersPanel.add(renderers.get(RendererType.HEADER).render(
				calendarContext));

		/* WEEK & DATES */
		HorizontalPanel datesPanel = new HorizontalPanel();
		if (weekNumber == true) {
			datesPanel.add(renderers.get(RendererType.WEEK).render(
					calendarContext));
			datesPanel.add(renderers.get(RendererType.DATES).render(
					calendarContext));
			renderersPanel.add(datesPanel);
		} else {
			renderersPanel.add(renderers.get(RendererType.DATES).render(
					calendarContext));
		}

		/* FOOTER */
		if (semanticNavigation == true) {
			renderersPanel.add(renderers.get(RendererType.FOOTER).render(
					calendarContext));
		}

		controller.handle(CalendarAction.INIT);
		rootPanel.add(renderersPanel);

		createHandlers();
	}

	/**
	 * Creates handlers
	 */

	private void createHandlers() {
		// TODO
		/*
		 * ExtraEffectsController c = new ExtraEffectsController(controller);
		 * rootPanel.addMouseOverHandler(c); rootPanel.addMouseOutHandler(c);
		 * rootPanel.addMouseWheelHandler(c); rootPanel.addMouseDownHandler(c);
		 * rootPanel.addMouseUpHandler(c);
		 */

		Iterator<IRenderer> it = renderers.values().iterator();
		for (; it.hasNext();) {
			IRenderer r = (IRenderer) it.next();
			rootPanel.addKeyDownHandler(r);
			rootPanel.addKeyUpHandler(r);
			rootPanel.addMouseWheelHandler(r);
		}
		rootPanel.addKeyDownHandler(controller);
		rootPanel.addFocusHandler(controller);
	}

	/**
	 * Sets the selection mode (SINGLE | MULTIPLE | NONE)
	 * 
	 * @param selectionMode
	 */

	public void setSelectionCardinality(SelectionCardinality selectionCard) {
		model.setSelectionCardinality(selectionCard);
	}

	/**
	 * Returns the selection mode (SINGLE | MULTIPLE | NONE)
	 * 
	 * @return the selection mode
	 */

	public SelectionCardinality getSelectionCardinality() {
		return model.getSelectionCardinality();
	}

	/**
	 * Creates an event
	 * 
	 * @param e
	 *            The event to create
	 */

	public void createEvent(final Event e) {
		controller.createEvent(e);
	}

	/**
	 * Returns the selected value
	 * 
	 * @return the selected date in the calendar
	 */

	public Date getValue() {
		return model.getSelectedDate();
	}

	/**
	 * Sets the calendar value to <i>d</i>
	 * 
	 * @param d
	 *            The date
	 */

	public boolean setValue(Date d) {
		return controller.setValue(d);
	}

	/**
	 * Sets the min date to be displayed
	 * 
	 * @param min
	 */

	public void setMinDate(Date min) {
		model.setMinDate(min);
	}

	/**
	 * Sets the max date to be displayed
	 * 
	 * @param max
	 */

	public void setMaxDate(Date max) {
		model.setMaxDate(max);
	}

	/**
	 * Returns the selected dates list (bounded by begin & end)
	 * 
	 * @param begin
	 * @param end
	 * @return
	 */

	public List<Date> getSelection(Date begin, Date end) {
		if (controller.getModel().getSelectionCardinality() == SelectionCardinality.NONE) {
			return null;
		}
		return controller.getModel().getSelectedDates(begin, end);
	}

	/**
	 * Returns the selected dates list (bounded by begin & end)
	 * 
	 * @param begin
	 * @param end
	 * @return
	 */

	public List<Date> getSelection(String begin, String end) {
		if (controller.getModel().getSelectionCardinality() == SelectionCardinality.NONE) {
			return null;
		}
		DateTimeFormat format = DateTimeFormat.getFormat("MM/dd/yyyy");
		Date b = format.parse(begin);
		Date e = format.parse(end);
		return controller.getModel().getSelectedDates(b, e);
	}

	/**
	 * Clear the selection
	 */

	public void clearSelection() {
		controller.fireModelChangedEvent(CalendarChangesMask.CLEAR_SELECTION,
				null);
	}

	/**
	 * Returns the calendar model
	 * 
	 * @return
	 */

	public CalendarModel getModel() {
		return this.model;
	}

	/**
	 * Returns the controller
	 * 
	 * @return
	 */

	public CalendarController getController() {
		return this.controller;
	}

	/**
	 * Registers a value change handler on the component
	 */

	@Override
	public HandlerRegistration addValueChangeHandler(
			ValueChangeHandler<Date> handler) {
		return addHandler(handler, ValueChangeEvent.getType());
	}

	/**
	 * Registers a selection change handler on the component
	 */

	@Override
	public HandlerRegistration addSelectionChangedHandler(
			SelectionChangedHandler handler) {
		return controller.addSelectionChangedHandler(handler);
	}

	/**
	 * Registers a invalid date handler on the component
	 */

	@Override
	public HandlerRegistration addInvalidDateHandler(InvalidDateHandler handler) {
		return controller.addInvalidDateHandler(handler);
	}

	public void focus() {
		this.rootPanel.setFocus(true);
	}
}